#!/usr/bin/env node

// mysql> alter user 'root'@'localhost' identified with mysql_native_password by '123456';
// mysql> flush privileges;

const os= require('os');
const mysql = require('mysql2');
const fs = require('fs-extra');
const path = require('path');
const moment = require('moment');
const command = require('./js/command');
const utils = require('./js/utils');
const _ = require('lodash');
const program = require('commander');
const shell = require('shelljs');
const configFile = path.join(os.homedir(), '.mysql_config');
const CACHE = {};
let _database = 'mysql';
let _host = 'localhost';
let _port = '3306';
let _user = 'root';
let _password = '123456';

let _verbose = false;
let _rawMode = false;
let _prettyShell = true;
let _highLightStyle;
let _limit = 1;
let _maxLimit = 1000;
async function createMysql (commands, options) {
    const db = mysql.createConnection({
        host: _host,
        port: _port,
        user: _user,
        password: _password,
        database: _database,
    });
    const tables = await getTables(null, db);
    for (const table of tables) {
        await getFieldsOfTable(null, db, table);
    }

    // 添加表，字段的自动完成
    function completer(line) {
        const words = line.replace(/\s,\s/g, ',').trimLeft().split(/\s/);
        const linePartial = _.last(_.last(words).split(','));
        const firstWord = _.toLower(words[0]);
        let list = [];
        if (words.length <= 1) {
            list = ['select', 'update', 'delete'].filter((o) => o.startsWith(linePartial));
        } else if (firstWord === 'd') {
            list = CACHE._tables.filter((o) => o.startsWith(linePartial));
        } else {
            const preWord = _.toLower(_.nth(words, -2));
            if (_.includes(['from', 'update', 'into', 'ls', 'cd', 's', 'm', 'r', 'c'], preWord)) {
                list = CACHE._tables.filter((o) => o.startsWith(linePartial));
            }  else {
                const prePreWord = _.toLower(_.nth(words, -3));
                if (_.includes(['s', 'm', 'r', 'c'], prePreWord)) {
                    const table = preWord;
                    const fields = CACHE[table];
                    list = fields.filter((o) => o.startsWith(linePartial));
                } else {
                    const prePrePreWord = _.toLower(_.nth(words, -4));
                    if (_.includes(['s', 'm', 'c'], prePrePreWord)) {
                        const table = prePreWord;
                        const fields = CACHE[table];
                        list = fields.filter((o) => o.startsWith(linePartial));
                    } else {
                        let allFileds = [];
                        for (const table of CACHE._tables) {
                            const fields = CACHE[table];
                            allFileds = [...allFileds, ...fields];
                        }
                        allFileds = _.uniq(allFileds);
                        list = allFileds.filter((o) => o.startsWith(linePartial));
                    }
                }
            }
        }
        const found = list.filter((o) => o.startsWith(linePartial));
        return [found.length ? found : list, linePartial];
    }

    CACHE._options = { ...options, db, completer };
    command(commands, CACHE._options);
    _verbose = true;
}
function formatRaw(obj) {
    if (!obj || !_.isObject(obj)) {
        return obj + `[${typeof obj}]`;
    }
    if (_.isArray(obj)) {
        return obj.map(o=>formatRaw(o));
    }
    for (const key in obj) {
        const item = obj[key];
        if (item instanceof Array) {
            obj[key] = formatRaw(item);
        } else if (item instanceof Buffer) {
            obj[key] = !!item.readInt8(0)+'[boolean]';
        } else if (item instanceof Date) {
            obj[key] = item + '[date]';
        } else {
            obj[key] = item + `[${typeof item}]`;
        }
    }
    return obj;
}
function formatResult(obj) {
    if (!obj || !_.isObject(obj)) {
        return obj;
    }
    if (_.isArray(obj)) {
        return obj.map(o=>formatResult(o));
    }
    for (const key in obj) {
        const item = obj[key];
        if (item instanceof Array) {
            obj[key] = formatResult(item);
        } else if (item instanceof Buffer) {
            obj[key] = !!item.readInt8(0);
        } else if (item instanceof Date) {
            obj[key] = moment(item).format('YYYY-MM-DD HH:mm:ss');
        }
    }
    return obj;
}
function formatCond(line) {
    if (!line) {
        return '';
    }
    if (/^1[0-9]{10}$/.test(line)) {
        return ` WHERE phone="${line}"`;
    }
    return ' WHERE ' + line
    .replace(/~[^&)|]+/g, o=>{let m = o[1]==='<'?o.slice(2):`%${o.substr(1)}`;return _.last(m)==='>'?` LIKE "${m.slice(0, -1)}"`:` LIKE "${m}%"`})
    .replace(/!=null|!=NULL/g, ' is not null ')
    .replace(/=null|=NULL/g, ' is null ')
    .replace(/(>=|<=|!=|=)([^&)|]+)/g, (o, m, n)=>`${m}${(m==='='||m==='!=')&&((n[0]!==`"`&& _.last(n)!==`"`)&&(n[0]!==`'`&& _.last(n)!==`'`))?`"${n}"`: n}`)
    .replace(/&/g, ' AND ')
    .replace(/\|/g, ' OR ')
    .replace(/#([\w,]*)/g, ' IN ($1)');
}
function formatSetFields(line, isReturnObject) {
    const list = [];
    let flag = true;
    let mode = false;
    while (flag) {
        let matches = line.match(/^([a-zA-Z][a-zA-Z0-9_]*):(.*)/);
        if (matches) {
            const key = matches[1];
            let value = matches[2];
            if (/^[^;]*=>{/.test(value)) { //带{函数
                mode = true;
                let i = -1, max = value.length, match = 0;
                while (i++ < max) {
                    const ch = value[i];
                    if (ch === '{') {
                        match++;
                    } else if (ch === '}') {
                        match--;
                    }
                    if ((ch === ';' || !ch) && match === 0) {
                        line = value.substr(i+1);
                        value = value.substr(0, i);
                        list.push({ key, value });
                        if (!line) {
                            flag = false;
                        }
                        break;
                    }
                }
                if (match !== 0) {
                    return null;
                }
            } else {
                matches = value.match(/([^;]*);(.*)/);
                if (matches) {
                    value = matches[1];
                    line = matches[2];
                }
                if (!mode && (/\$\w+/.test(value) || /^[^;]*=>/.test(value))) {
                    mode = true;
                }
                list.push({ key, value });
                if (!matches) {
                    break;
                }
            }
        } else {
            return null;
        }
    }
    if (!mode && !isReturnObject) {
        return list.map(o=>`${o.key}=${o.value}`).join(',');
    }
    return list;
}
function formatUpdate(self, doc, setFields) {
    if (!setFields) {
        return {};
    }
    const obj = {};
    for (const item of setFields) {
        const key = item.key;
        let value = item.value||'';

        // 解析函数
        value = utils.parseFunction(value, true);
        if (_.isFunction(value)) {
            obj[key] = value(doc[key], {_, utils, moment, $$: doc});
            continue;
        }
        obj[key] = value;
    }
    return obj;
}
async function formatFields(self, db, table, line) {
    if (!line || line === '*') {
        return '*';
    }
    if (/\(/.test(line)) {
        return line;
    }
    let method = _.filter;
    if (line[0] === '-') {
        method = _.reject;
        line = line.substr(1);
    }
    let list = line.split(',');
    const allFields = await getFieldsOfTable(self, db, table);
    list = list.map(o => new RegExp(o.replace('<', '^').replace('>', '$'), 'i'));

    return method(allFields, o=>_.some(list, r=>r.test(o))).join(',');
}
async function formatSort(self, db, table, line) {
    if (!line) {
        return '';
    }
    const sort = [];
    const allFields = await getFieldsOfTable(self, db, table);
    line = line.split(',');
    for (const i in line) {
        let item = line[i];
        let order = item[0] === '+' ? 'ASC' : 'DESC';
        item = item.substr(1);
        if (_.includes(allFields, item)) {
            sort.push(`${item} ${order}`);
        }
    }
    if (!sort.length) {
        return '';
    }
    return ` ORDER BY ${sort.join(',')}`;
}
function formatLine(self, line) {
    let i = -1, max = line.length - 1, newLine = '', amatch = 0, bmatch = 0, cmatch = 0;
    while (i++ < max) {
        let ch = line[i];
        if (ch === '[') {
            amatch++;
        } else if (ch === ']') {
            amatch--;
        }
        if (ch === '{') {
            bmatch++;
        } else if (ch === '}') {
            bmatch--;
        }
        if (ch === '(') {
            cmatch++;
        } else if (ch === ')') {
            cmatch--;
        }
        if (amatch < 0 || bmatch < 0 || cmatch < 0) {
            self.error('括号不匹配');
            return [];
        }
        if (!(ch === ' ' && (amatch || bmatch || cmatch))) {
            newLine += ch;
        }
    }
    if (amatch || bmatch || cmatch) {
        self.error('括号不匹配');
        return [];
    }
    return newLine
    .replace(/"[^"]+"/g, o=>o.replace(' ', '&nbsp;'))
    .replace(/'[^']+'/g, o=>o.replace(' ', '&nbsp;'))
    .replace(/\s*:\s*/g, ':')
    .replace(/\s*;\s*/g, ';')
    .replace(/\s*,\s*/g, ',')
    .replace(/\s*!=\s*/g, '!=')
    .replace(/\s*=\s*/g, '=')
    .replace(/\s*~\s*/g, '~')
    .replace(/\s*\|\s*/g, '|')
    .replace(/\s*&\s*/g, '&')
    .replace(/\s*#\s*/g, '#')
    .trim().split(/\s/).filter(o=>o).map(o=>o.replace('&nbsp;', ' '));
}
async function getTables(self, db, force) {
    if (!CACHE._tables || force) {
        CACHE._tables = (await querySQL(self, db, 'show tables')).map(o=>o[_.keys(o)[0]]);
    }
    return CACHE._tables;
}
async function getFieldsOfTable(self, db, table, force) {
    if (!CACHE[table] || force) {
        CACHE[table] = (await querySQL(self, db, 'desc ' + table)).map(o=>o.Field);
    }
    return CACHE[table];
}
function querySQL(self, db, query) {
    if (_verbose && self) {
        self.verbose(query);
    }
    return new Promise(resolve=>{
        db.query(query, (err, results) => {
            if (err) {
                if (self) {
                    self.error(err);
                    self.prompt();
                }
                resolve([]);
            } else {
                resolve(results);
            }
        });
    })
}
async function executeSQL(self, db, line, noPrint) {
    const results = await querySQL(self, db, line);
    if (noPrint) {
        return results.forEach ? results.map(o=>formatResult(o)) : results;
    }
    if (results.forEach) {
        results.forEach(o=>{
            showJson(self, formatResult(o));
        });
    } else {
        showJson(self, results);
    }
    self.prompt();
}
async function executeLine(line, { db }) {
    if (!line) {
        return this.prompt();
    }
    await executeSQL(this, db, line);
}
function showJson(self, docs) {
    if (_rawMode) {
        self.showJson(formatRaw(docs), _prettyShell, _highLightStyle);
    } else {
        self.showJson(formatResult(docs), _prettyShell, _highLightStyle);
    }
}
async function showTables(line, { db }) {
    if (!line || line === '.') { // show tables
        this.print('tables in '+ _database +' :', 'red');
        const tables = await getTables(this, db, true);
        this.print(tables.join('  '), 'green');
        this.prompt();
    } else if (line === '..' || line === '-') { // show databases
        this.print('databases :', 'red');
        const results = await querySQL(this, db, 'show databases');
        this.print(results.map(o=>o.Database).join('  '), 'green');
        this.prompt();
    } else { // show table fields
        line = line.replace(/[^\w]*/g, '');
        const fields = await getFieldsOfTable(this, db, line, true);
        this.print(fields.join('  '), 'green');
        this.prompt();
    }
}
function getLimitIndex(list) {
    return _.findIndex(list, o=>+o==o && o<=_maxLimit);
}
function getCondIndex(list) {
    return _.findIndex(list, o=>/!=|>=|[^=]>[\w'"]|=[\w\u4e00-\u9fa5'"]|<=|<[\w'"]|~|#/.test(o) || /^[0-9a-z]{24}$/.test(o) || /^1[0-9]{10}$/.test(o) || /^[0-9]{17}[0-9xX]$/.test(o));
}
function getSortIndex(list) {
    return _.findIndex(list, o=>/[a-zA-Z][a-zA-Z0-9_]*(\+|-)$/.test(o));
}
function getSetFieldsIndex(list) {
    return _.findIndex(list, o=>/^[a-zA-Z][a-zA-Z0-9_]*:/.test(o));
}
async function showTableRows (line, { db }) {
    if (!line) {
        return this.prompt();
    }
    const matches = line.match(/out=[^\s]*/);
    let writeFile;
    if (matches) {
        writeFile = matches[0].replace('out=', '');
        line = line.replace(matches[0], '');
    }

    let showCount = false;
    let list = formatLine(this, line);
    const table = list[0];
    if (!table) {
        return this.error('invalid oper');
    }
    list = _.drop(list);

    let limit = '';
    let limitIndex = getLimitIndex(list);
    if (limitIndex !== -1) {
        limit = +list[limitIndex];
        if (limit < 0) {
            showCount = true;
        }
        limit = limit ? ` LIMIT ${limit}` : '';
        list.splice(limitIndex, 1);
    } else {
        limit = ` LIMIT 1`;
    }

    let sort = '';
    let sortIndex = getSortIndex(list);
    if (sortIndex !== -1) {
        sort = await formatSort(this, db, table, list[sortIndex]);
        list.splice(sortIndex, 1);
    }

    let cond = '';
    let condIndex = getCondIndex(list);
    if (condIndex !== -1) {
        cond = formatCond(list[condIndex]);
        list.splice(condIndex, 1);
    }

    let setFields;
    let setFieldsIndex = getSetFieldsIndex(list);
    if (setFieldsIndex !== -1) {
        setFields = formatSetFields(list[setFieldsIndex]);
        list.splice(setFieldsIndex, 1);
    }

    if (showCount) {
        return await executeSQL(this, db, `SELECT COUNT(*) FROM ${table}${cond}`);
    }
    if (setFields) {
        if (typeof setFields === 'string') {
            await querySQL(this, db, `UPDATE ${table} SET ${setFields}${cond}${sort}${limit}`);
            return await executeSQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
        }
        const docs = await querySQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
        for (const doc of docs) {
            cond = ` WHERE id="${doc.id}"`;
            const update = _.map(formatUpdate(this, doc, setFields), (o,k)=>`${k}=${o}`).join(',');
            await querySQL(this, db, `UPDATE ${table} SET ${update}${cond}`);
        }
        return executeSQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
    }

    let hasRemove;
    let removeIndex = _.findIndex(list, o=>o==='r');;
    if (removeIndex !== -1) {
        hasRemove = true;
        list.splice(removeIndex, 1);
    }
    if (hasRemove) {
        return await executeSQL(this, db, `DELETE FROM ${table} ${cond}${sort}${limit}`);
    }

    const fields = await formatFields(this, db, table, list[0]);
    const results = await executeSQL(this, db, `SELECT ${fields} FROM ${table}${cond}${sort}${limit}`, writeFile);
    if (writeFile) {
        fs.writeFileSync(writeFile, `module.exports=${JSON.stringify(formatResult(results), null, 2)};`);
        this.print('write result to ' + writeFile);
        this.prompt();
    }
}
async function modifyTable(line, { db }) {
    if (!line) {
        return this.prompt();
    }
    let list = formatLine(this, line);
    const table = list[0];
    if (!table) {
        return this.error('invalid oper');
    }
    list = _.drop(list);

    let limit = '';
    let limitIndex = getLimitIndex(list);
    if (limitIndex !== -1) {
        limit = +list[limitIndex];
        limit = limit ? ` LIMIT ${limit}` : '';
        list.splice(limitIndex, 1);
    } else {
        limit = ` LIMIT ${_limit}`;
    }

    let sort = '';
    let sortIndex = getSortIndex(list);
    if (sortIndex !== -1) {
        sort = await formatSort(this, db, table, list[sortIndex]);
        list.splice(sortIndex, 1);
    }

    let cond = '';
    let condIndex = getCondIndex(list);
    if (condIndex !== -1) {
        cond = formatCond(list[condIndex]);
        list.splice(condIndex, 1);
    }

    let setFieldsIndex = getSetFieldsIndex(list);
    if (setFieldsIndex === -1) {
        return this.error('no set fields');
    }
    const setFields = formatSetFields(list[setFieldsIndex]);
    if (!setFields) {
        return this.error('set fields error');
    }
    list.splice(setFieldsIndex, 1);
    if (typeof setFields === 'string') {
        await querySQL(this, db, `UPDATE ${table} SET ${setFields}${cond}${sort}${limit}`);
        return await executeSQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
    }

    const docs = await querySQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
    for (const doc of docs) {
        cond = ` WHERE id="${doc.id}"`;
        const update = _.map(formatUpdate(this, doc, setFields), (o,k)=>`${k}=${o}`).join(',');
        await querySQL(this, db, `UPDATE ${table} SET ${update}${cond}`);
    }
    return executeSQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
}
async function removeTable(line, { db }) {
    if (!line) {
        return this.prompt();
    }
    let list = formatLine(this, line);
    const table = list[0];
    if (!table) {
        return this.error('invalid oper');
    }
    list = _.drop(list);

    let limit = '';
    let limitIndex = getLimitIndex(list);
    if (limitIndex !== -1) {
        limit = +list[limitIndex];
        limit = limit ? ` LIMIT ${limit}` : '';
        list.splice(limitIndex, 1);
    } else {
        limit = ` LIMIT ${_limit}`;
    }

    let sort = '';
    let sortIndex = getSortIndex(list);
    if (sortIndex !== -1) {
        sort = await formatSort(this, db, table, list[sortIndex]);
        list.splice(sortIndex, 1);
    }

    let cond = '';
    let condIndex = getCondIndex(list);
    if (condIndex !== -1) {
        cond = formatCond(list[condIndex]);
        list.splice(condIndex, 1);
    }

    await executeSQL(this, db, `DELETE FROM ${table} ${cond}${sort}${limit}`);
}
async function copyTableRows(line, { db }) {
    if (!line) {
        return this.prompt();
    }
    let list = formatLine(this, line);
    const table = list[0];
    if (!table) {
        return this.error('invalid oper');
    }
    list = _.drop(list);

    let limit = '';
    let limitIndex = getLimitIndex(list);
    if (limitIndex !== -1) {
        limit = +list[limitIndex];
        limit = limit ? ` LIMIT ${limit}` : '';
        list.splice(limitIndex, 1);
    } else {
        limit = ` LIMIT ${_limit}`;
    }

    let sort = '';
    let sortIndex = getSortIndex(list);
    if (sortIndex !== -1) {
        sort = await formatSort(this, db, table, list[sortIndex]);
        list.splice(sortIndex, 1);
    }

    let cond = '';
    let condIndex = getCondIndex(list);
    if (condIndex !== -1) {
        cond = formatCond(list[condIndex]);
        list.splice(condIndex, 1);
    }

    let setFieldsIndex = getSetFieldsIndex(list);
    let setFields = null;
    if (setFieldsIndex !== -1) {
        setFields = list[setFieldsIndex];
        setFields = formatSetFields(setFields, true);
        if (!setFields) {
            return this.error('set fields error');
        }
        list.splice(setFieldsIndex, 1);
    }

    const docs = await querySQL(this, db, `SELECT * FROM ${table}${cond}${sort}${limit}`);
    const fields = await getFieldsOfTable(this, db, table);
    for (const doc of docs) {
        const update = formatUpdate(this, doc, setFields);
        const values = fields.map(o=>{
            const item = update[o] || doc[o];
            if (item instanceof Date) {
                return `"${moment(item).format('YYYY-MM-DD HH:mm:ss.SSS')}"`;
            }
            if (item !== undefined) {
                return JSON.stringify(item);
            }
            return 'NULL';
        });
        await querySQL(this, db, `INSERT INTO ${table} (${fields.join(',')}) VALUES (${values.join(',')})`);
        cond = ` WHERE id="${update.id}"`;
        return executeSQL(this, db, `SELECT * FROM ${table}${cond}`);
    }
}
function formatTableFields(setFields) {
    const keyList = [];
    const defineList = [];
    const valueList = [];
    for (const item of setFields) {
        const key = item.key;
        let value = item.value||'';

        keyList.push(key);

        // 解析函数
        value = utils.parseFunction(value, true);
        if (_.isFunction(value)) {
            value = value('', {_, utils, moment});
        }
        if (value === 'true' || value === 'false') {
            defineList.push(`${key} VARCHAR(5)`);
            valueList.push(`'${value}'`);
            continue;
        }
        if (+value == value) {
            defineList.push(`${key} INT`);
            valueList.push(+value);
            continue;
        }
        if (/\+|-|\*|\//.test(value)) {
            try {
                let _val = eval(value);
                if (typeof _val === 'number') {
                    defineList.push(`${key} INT`);
                    valueList.push(_val);
                    continue;
                } else {
                    _val = value.replace(/^'(.*)'$/, '$1');
                    // 解析时间
                    if (/^\d{4}-\d{2}-\d{2}$/.test(_val)) {
                        defineList.push(`${key} DATE`);
                        valueList.push(`'${_val}'`);
                        continue;
                    }
                    if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(_val)) {
                        defineList.push(`${key} DATETIME`);
                        valueList.push(`'${_val}'`);
                        continue;
                    }
                }
            } catch(err) {}
        }
        if (/^'.*'$|^".*"$/.test(value)) {
            value = value.replace(/^['"](.*)['"]$/, '$1');
        }
        defineList.push(`${key} VARCHAR(256)`);
        valueList.push(`'${value}'`);
    }
    return { keyList, defineList, valueList };
}
async function saveTableRow(line, { db }) {
    if (!line) {
        return this.prompt();
    }
    let list = formatLine(this, line);
    const table = list[0];
    if (!table) {
        return this.error('invalid oper');
    }
    list = _.drop(list);

    let setFieldsIndex = getSetFieldsIndex(list);
    let setFields = null;
    if (setFieldsIndex !== -1) {
        setFields = list[setFieldsIndex];
        setFields = formatSetFields(setFields, true);
        if (!setFields) {
            return this.error('set fields error');
        }
        list.splice(setFieldsIndex, 1);
    }

    const { keyList, defineList, valueList } = formatTableFields(setFields);
    const cmd = `
    CREATE TABLE IF NOT EXISTS \`${table}\`(
        \`id\` INT UNSIGNED AUTO_INCREMENT,
        ${defineList.join(',')},
        PRIMARY KEY (\`id\`)
    )ENGINE=InnoDB DEFAULT CHARSET=utf8
    `;
    await executeSQL(this, db, cmd);
    await executeSQL(this, db, `INSERT INTO ${table} (${keyList.join(',')}) VALUES (${valueList.join(',')})`);
}
function togglePrettyShell() {
    _prettyShell = !_prettyShell;
    this.print('pretty format change to ' + _prettyShell);
    this.prompt();
}
function toggleVerbose() {
    _verbose = !_verbose;
    this.print('verbose format change to ' + _verbose);
    this.prompt();
}
function toggleRawMode() {
    _rawMode = !_rawMode;
    this.print('rawMode format change to ' + _rawMode);
    this.prompt();
}
function setHighLightStyle(value) {
    _highLightStyle = value == 0 ? 'railscasts' : 'github';
    this.print('highLightStyle set to ' + _highLightStyle);
    this.prompt();
}
function setMaxLimit(line) {
    _maxLimit = +line || 1000;
    this.print('set limit to ' + _maxLimit);
    this.prompt();
}
async function changeToDatabase(line, { db }) {
    if (!line) {
        this.prompt();
        return;
    }
    db.close();
    _database = line;
    db = mysql.createConnection({
        host: _host,
        user: _user,
        password: _password,
        database: _database,
    });
    for (const table of CACHE._tables) {
        delete CACHE[table];
    }
    CACHE._options.db = db;
    const tables = await getTables(this, db, true);
    for (const table of tables) {
        await getFieldsOfTable(this, db, table, true);
    }
    fs.writeJSONSync(configFile, { host: _host, port: _port, user: _user, password: _password, database: _database });
    this.prompt();
}
function dumpTables(line) {
    let list = line.split(/\s+/).filter(o=>o);
    let file = _.find(list, o=>/\.sql$/.test(o));
    if (file) {
        _.remove(list, o=>o===file);
    } else {
        file = `${_database}.sql`;
    }
    const cmd =`mysqldump -uroot -p123456 ${_database} ${list.join(' ')} > ${file}`;
    shell.exec(cmd, { silent:true }, (code, stdout, stderr) => {
        if (stderr && /error/.test(stderr)) {
            this.error(stderr);
            this.error(`目标文件：${process.cwd()}/${file}`);
            return this.prompt();
        }
        this.success(`目标文件：${process.cwd()}/${file}`);
        this.prompt();
    });
}
function showHelp (isFull) {
    this.print('commands:', 'blue');
    this.print('    <h|help>: show help');
    this.print('    <q|exit>: exit');
    this.print('    cd: change to database');
    this.print('    ls: showTables|ls ..: showDatabases|ls table: showTableFields');
    this.print('    s(showTableRows): s table f1,f2>,<f3> (f1=xx&f2=yy)|f3=zz|f4~<xx>|f5#1,2,3 limit(-1:count) sortField[+-] [out=file] [r] [f1:$f1+1]');
    this.print('    m(modifyTable): m table f1:$f1+1;f2:xx;f3:(doc, {_, utils, moment})=>xx $cond limit sortField[+-]');
    this.print('    r(removeTable): # r table $cond limit sortField[+-]');
    this.print('    c(copyTableRows): c table $update $cond limit sortField[+-]');
    isFull && this.print('    d(dumpTables): d [table1 table2 xx.sql]');
    isFull && this.print('    save(saveTableRow): save table f1:xx[,f2:yy]');
    isFull && this.print('    parseFunction:=>xx|return xx|($, {_, moment, utils, $$})=>xx|@xx.js|=x$x');
    isFull && this.print('    _: togglePrettyShell');
    isFull && this.print('    v: toggleVerbose');
    isFull && this.print('    hl: setHighLightStyle 0: railscasts 1: github');
    isFull && this.print('    limit: setMaxLimit');
    this.print('');
    this.prompt();
}
function showFullHelp() {
    showHelp.call(this, true);
}
const COMMANDS = {
    'h': showHelp,
    'help': showFullHelp,
    'q|exit': function () { this.close() },
    'cls': function() { this.clear() },
    'cd': changeToDatabase,
    'ls': showTables,
    's': showTableRows,
    'm': modifyTable,
    'r': removeTable,
    'c': copyTableRows,
    'save': saveTableRow,
    'd': dumpTables,
    '_': togglePrettyShell,
    'v': toggleVerbose,
    'r': toggleRawMode,
    'hl': setHighLightStyle,
    'limit': setMaxLimit,
    'default': executeLine,
};


program
.version('1.0.0')
.option('-t, --host [host]', 'set host', '')
.option('-o, --port [port]', 'set port', '')
.option('-u, --user [user]', 'set user', '')
.option('-p, --password [password]', 'set password', '')
.option('-d, --database [database]', 'set database', '')
.option('-c, --config', 'show config', '')
.parse(process.argv);

let options = {};
try {
    options = fs.readJSONSync(configFile);
} catch (e) {}
_host = program.host || options.host || _host;
_database = program.database || options.database || _database;
_port = program.port || options.port || _port;
_user = program.user || options.user || _user;
_password = program.password || options.password || _password;

if (options.host !== _host || options.database !== _database || options.port !== _port || options.user !== _user || options.password !== _password) {
    fs.writeJSONSync(configFile, { host: _host, port: _port, user: _user, password: _password, database: _database });
}
if (program.config) {
    console.log({ host: _host, port: _port, user: _user, password: _password, database: _database });
    process.exit(0);
}

createMysql(COMMANDS, { title: 'mysql', history: [ 's', 'm', 'r', 'c', 'd', 'save' ] });
